import unittest
from collections import defaultdict
from asynctools import UrlFetchTask, CachedMultiTask

class CachedMultiTaskTests(unittest.TestCase):
    
    def setUp(self):
        self.counting_url_fetcher = CountingUrlFetcher()
        self.counting_memcache = CountingMemcache()
        self.url_1 = 'http://example.com/1'
        self.url_2 = 'http://example.com/2'
        self.task_1 = UrlFetchTask(url=self.url_1, urlfetch=self.counting_url_fetcher)
        self.task_2 = UrlFetchTask(url=self.url_2, urlfetch=self.counting_url_fetcher)

    def test_cache_miss_causes_runner_execution(self):
        runner = CachedMultiTask(tasks=[self.task_1, self.task_2], memcache=self.counting_memcache)
        runner.run()
        self.assertEquals(1, self.counting_url_fetcher.url_count[self.url_1])
        self.assertEquals(1, self.counting_url_fetcher.url_count[self.url_2])
        
    def test_items_are_cached(self):
        runner = CachedMultiTask(tasks=[self.task_1, self.task_2], memcache=self.counting_memcache)
        runner.run()
        self.assertEquals(1, self.counting_memcache.set_count[self.task_1.cache_key])
        self.assertEquals(1, self.counting_memcache.set_count[self.task_2.cache_key])
        
    def test_partial_hit_returns_both_results(self):
        runner = CachedMultiTask(tasks=[self.task_1], memcache=self.counting_memcache)
        runner.run()
        self.assertEquals(1, self.counting_memcache.set_count[self.task_1.cache_key])
        self.assertEquals(1, self.counting_url_fetcher.url_count[self.url_1])
        runner = CachedMultiTask(tasks=[self.task_1, self.task_2], memcache=self.counting_memcache)
        runner.run()
        self.assertEquals(1, self.counting_memcache.hit_count[self.task_1.cache_key]) # task_1 hit
        self.assertEquals(1, self.counting_memcache.miss_count[self.task_2.cache_key]) # task_2 missed
        self.assertEquals(1, self.counting_url_fetcher.url_count[self.url_1]) # task_1 urlfetch did not occur (occurred once above, should not have occurred a second time)
        self.assertEquals(1, self.counting_url_fetcher.url_count[self.url_2]) # task_2 urlfetch did occur
        self.assertEquals(1, self.counting_memcache.set_count[self.task_1.cache_key]) # task_1 was not set (occurred once above, should not have occurred a second time)
        self.assertEquals(1, self.counting_memcache.set_count[self.task_2.cache_key]) # task_2 was set
        self.assert_(self.task_1.get_result())
        self.assert_(self.task_2.get_result())
        
    def test_cache_expiration_removes_items(self):
        runner = CachedMultiTask(tasks=[self.task_1, self.task_2], memcache=self.counting_memcache)
        runner.run()
        self.assertEquals(1, self.counting_memcache.set_count[self.task_1.cache_key])
        self.assertEquals(1, self.counting_memcache.set_count[self.task_2.cache_key])
        expiration_runner = CachedMultiTask(tasks=[self.task_1], memcache=self.counting_memcache)
        expiration_runner.delete_cached_entries()
        self.assertEquals(1, self.counting_memcache.delete_count[self.task_1.cache_key])
        self.assertEquals(0, self.counting_memcache.delete_count[self.task_2.cache_key])
        

class CountingUrlFetcher(object):
    
    def __init__(self, *args, **kwargs):
        self.url_count = defaultdict(int)
        
    def create_rpc(self, deadline=None, callback=None):
        return MockRpc()
        
    def make_fetch_call(self, rpc, url, *args, **kwargs):
        """
        Instead of making the call, just record the fact that an attempt was made.
        """
        self.url_count[url] += 1


class MockRpc(object):
    def __init__(self):
        pass
        
    def wait(self):
        pass
        
    def get_result(self):
        return "result"


class CountingMemcache(object):
    def __init__(self):
        self._cache = {}
        self.get_count = defaultdict(int)
        self.set_count = defaultdict(int)
        self.hit_count = defaultdict(int)
        self.miss_count = defaultdict(int)
        self.delete_count = defaultdict(int)
    
    def get_multi(self, cache_keys, namespace=None):
        result = {}
        for key in cache_keys:
            self.get_count[key] += 1
            if key in self._cache:
                self.hit_count[key] += 1
                result[key] = self._cache[key]
            else:
                self.miss_count[key] += 1
        return result
        
    def set_multi(self, set_dict, time=0, namespace=None):
        for key in set_dict.keys():
            self.set_count[key] += 1
        self._cache.update(set_dict)
        return []

    def delete_multi(self, cache_keys, seconds=0, namespace=None):
        for key in cache_keys:
            self.delete_count[key] += 1
            if key in self._cache:
                self._cache.pop(key)
        return True


if __name__ == '__main__':
    unittest.main()
